﻿using System;
using System.Linq;
using System.Net.DnsToolkit.Packet;
using System.Net.DnsToolkit.Serializer.Stream.Extension;
using System.Net.DnsToolkit.Util;
using System.Net.DnsToolkit.Packet.Busy;

namespace System.Net.DnsToolkit.Bastard.PacketGenerator {

    /// <summary>
    /// Creates DNS packet that has domain name compression pointer
    /// that point to next question, that point to next question, an so on.
    /// Le last question point to the first, thus, creating an infinite loop.
    /// 
    /// Some explaination on the bug :
    /// ---------------------------------------------------------
    ///   Normal packet, compression disabled
    /// ---------------------------------------------------------
    /// 
    /// Message {
    ///     Header,
    ///     Questions {
    ///         Question0 {
    ///             Name = "question0."
    ///         }
    ///         Question1 {
    ///             Name = "question1."
    ///         },
    ///         ...,
    ///         QuestionX {
    ///             Name = "questionX."
    ///         }
    ///     }
    ///     Answers,
    ///     Authorities,
    ///     Additionals
    /// }
    /// 
    /// 
    /// ---------------------------------------------------------
    ///   Domain compression multi question loop
    /// ---------------------------------------------------------
    /// 
    /// Message {
    ///     Header,
    ///     Questions {
    ///         Question0 {
    ///             Name = "question0." + pointer to question1
    ///         }
    ///         Question1 {
    ///             Name = "question1." + pointer to question2
    ///         },
    ///         ...,
    ///         QuestionX {
    ///             Name = "questionX." + pointer to question0
    ///         }
    ///     }
    ///     Answers,
    ///     Authorities,
    ///     Additionals
    /// }
    /// 
    /// 
    /// 
    /// 
    /// </summary>
    public class DomainPointerMultiLoop : IPacketGenerator {

        private readonly string[] _names;

        public DomainPointerMultiLoop()
            : this("q0", "q1", "q2", "q3", "q4") {
        }

        public DomainPointerMultiLoop(params string[] names) {
             _names = names;
        }


        public byte[] GetBytes() {

            var useCompression = false;
            byte[] bs = GetMessage().GetBytes(useCompression);

            if (bs.Length <= 12) {   // questionless packet
                return bs;
            }

            const byte baseq1 = 12;  // question1 is always at offset 12
            const int qtlength = 6;  // question tail (null+qclass+qtype) = 6

            int basep = baseq1;      // base pointer is current question base offset
            int length;              // current question length
            int endp;                // offset of question name's end offset
            int nextp;               // next pointer is next question base offset

            
            do {

                length = bs[basep];
                endp = basep + length;
                nextp = endp + qtlength;

                bs[basep]--;
                bs[endp] = CompressionConstant.NameCompression;
                bs[endp+1] = (byte)nextp;

                basep = nextp;


            } while(nextp < bs.Length);

            bs[endp + 1] = (byte)baseq1; // make last question pointer, points to q1

            return bs;
        }


        public IMessage GetMessage() {

            var message = new Message();
            var qs = from s in _names
                     select new Question {
                         QName = new DomainName {
                             Name = s +'p'
                         },
                         QClass = QClassEnum.IN,
                         QType = QTypeEnum.A
                         
                     };
            message.Questions.AddRange(qs.ToArray());
            message.Header.QuestionCount = (ushort)message.Questions.Count;

            return message;
        }

    }
}
